// 定义游戏控制类
// 引入食物类
import Food from './Food'
// 引入信息类
import ScorePanel from './ScorePanel';
// 引入🐍类
import Snake from './Snake';
// 引入方向类型
import { direction } from '../type/type';

// 设置合法的键盘方向列表
const direction: string[] = ['ArrowUp', 'ArrowRight', 'ArrowDown', 'ArrowLeft'];

export default class GameControl {
    private snake: Snake = new Snake();
    private food: Food = new Food();
    private scorePanel: ScorePanel = new ScorePanel(10, 6);
    // 游戏的定时器
    private timer: any = undefined;
    // 表示是否死亡
    public hasDead: boolean = false;

    constructor() {
        this.init();
    }

    // 游戏初始化
    init() {
        // 1.绑定键盘事件
        document.addEventListener('keydown', this.keydownHandler)
        document.addEventListener('keyup', this.keyupHandler)
        this.reset();
    }

    // 重新开始
    reset() {
        // 0.把🐍的长度和头的位置重置
        this.snake.reset();
        // 2. 把🐍变活
        this.hasDead = false;
        // 3. 把分数清空
        this.scorePanel.reset();
        // 3. 隐藏信息栏
        this.scorePanel.showDeadMsg('none');
        // 4. 随机位置产生食物
        this.changeFoodPosition();
        // 5. 显示食物和🐍
        this.showFAS('flex','block');
        // 6. 🐍开始移动
        // timer是不能直接修改间隔时间的
        this.timer = setInterval(() => {
            this.run();
        }, 500 - this.scorePanel.level * 25);
    }

    // 控制🐍的运动
    run=()=> {
        // 判断🐍是否将吃到食物
        const willEat = this.willEatFood();
        // 🐍开始移动
        this.snake.snakeRun(willEat);
        // 加分
        willEat && this.scorePanel.addScore();
        // 判断🐍是否死亡
        this.HasDie();
        // 🐍吃完了食物，修改食物的位置
        willEat && this.changeFoodPosition();
    }

    // 判断是否要吃到食物
    willEatFood() {
        // 获取🐍头下一位置坐标
        const [headX, headY] = this.snake.getHeadNextPosition();
        const { leftX, topY } = this.food.position;
        if (leftX === headX && topY === headY) {
            return true;
        }
        return false;
    }

    // 判断🐍是否将要死亡
    HasDie() {
        const [headX, headY] = this.snake.getSnakeHeadPosition();
        // 1. 判断是否撞墙
        const willKnockAtWall = (headX > 290 || headX < 0 || headY > 290 || headY < 0);
        // 2. 判断是否吃到自己
        const eatSelf = () => {
            for (let snakeBody of this.snake.snakeBody.slice(1)) {
                const [leftX, topY] = snakeBody.position;
                if (headX == leftX && headY == topY) return true;
            }
            return false;
        }

        // 死亡之后的逻辑
        if (willKnockAtWall || eatSelf()) {
            // 关闭定时器 
            clearInterval(this.timer);
            // 改变🐍的生存状态
            this.hasDead = true;
            // 展示游戏结束信息
            this.scorePanel.showDeadMsg('block');
            // 隐藏食物和🐍
            this.showFAS('none','none');
        }
    }

    // 显示/隐藏食物和🐍
    showFAS(msg1:string,msg2:string){
        this.food.showFood(msg1);
        this.snake.showSnake(msg2);
    }

    // 修改食物的位置
    changeFoodPosition() {
        // 食物的宽度是10px，舞台的宽度是300px，一共有30格，所以食物的位置只能是0-29格
        function getRandomBlock(): number {
            return Math.floor(Math.random() * 30) * 10;
        }
        this.food.position = { leftX: getRandomBlock(), topY: getRandomBlock() }
    }

    // 键盘按下的响应函数
    // key的可选值{ArrowUp,ArrowRight,ArrowDown,ArrowLeft}
    keydownHandler = (event: KeyboardEvent) => {
        const key = event.key;
        // 修改🐍的运动方向
        if (direction.includes(key)) {
            const res=this.snake.changeDirection(key as direction);
            // 判断方向是否修改成功
            if(res){
                // 为了让用户按下按键之后，🐍马上响应一次运动，需要主动调用一次run()
                // 但是主动调用run的时候容易与定时器的调用相邻触发，导致🐍的位置突变
                // 我们需要在主动调用run之前关闭定时器，run调用完毕之后再打开
                this.restartTimer(0,true);
            }
        }
        // 加速
        else if (key === 'Shift') {
            this.restartTimer(250);
        }
    }

    // 键盘松开的响应函数
    keyupHandler = (event: KeyboardEvent) => {
        const key = event.key;
        if (key === 'Shift') {
            this.restartTimer();
        }
    }

    // 关闭旧定时器开启新定时器
    restartTimer(speed:number=0,bool:boolean=false) {
        clearInterval(this.timer);
        if(bool) {
            this.run();
        }
        this.timer = setInterval(() => {
            this.run();
        }, 500 - speed - 25 * this.scorePanel.level)
    }

}